﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using Xant.Querier.Core;

namespace Xant.Querier.Compilers
{

    /// <summary>
    /// Represents the query specification compiler which can compile the query specification
    /// into a WHERE clause of a SQL statement for RDBMS.
    /// </summary>
    public abstract class SqlWhereClauseCompiler : QueryCompiler<string>
    {
        /// <summary>
        /// The parameter values.
        /// </summary>
        private readonly Dictionary<string, object> parameterValues = new Dictionary<string, object>();

        /// <summary>
        /// 编译SQL WHERE条件从句时，是否以参数方式传递变量，否则将以明文嵌入在条件从句中
        /// </summary>
        public bool UseParameter { get; set; }

        /// <summary>
        /// 编译时是否生成SELECT部分
        /// </summary>
        public bool GenSelectPart { get; set; }

        protected virtual char LikeSymbol
        {
            get
            {
                return '%';
            }
        }

        protected virtual char ParameterChar
        {
            get
            {
                return '@';
            }
        }

        /// <summary>
        /// 编译SQL WHERE条件从句时生成的参数(当<see cref="UseParameter"/>参数值为true时)
        /// </summary>
        public IEnumerable<KeyValuePair<string, object>> ParameterValues
        {
            get
            {
                return this.parameterValues;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="SqlWhereClauseCompiler"/> class.
        /// </summary>
        /// <param name="query">查询对象<see cref="Query"/></param>
        public SqlWhereClauseCompiler(Query query):base(query)
        {
            //如果指定了分页，那么就自动开启生成SELECT开关
            if (query.Pagination != null)
            {
                this.GenSelectPart = true;
            }
        }

        /// <summary>
        /// 如果字段路径包含多级(如Supplier.Code)，则生成Exists条件语句
        /// </summary>
        /// <param name="criteria"></param>
        /// <param name="sb"></param>
        /// <param name="config"></param>
        /// <param name="propertyQueue"></param>
        private void ParseRelationship(Criteria criteria, StringBuilder sb, EntityConfiguration config,
            Queue<string> propertyQueue)
        {
            var propertyName = propertyQueue.Dequeue();
            if (!propertyQueue.Any()) //如果是最后一个元素
            {
                var s = GetCriteriaPresentation(criteria);
                sb.Append(s);
            }

            var relationship = config.Relationships[propertyName];
            if (relationship != null)
            {
                string format;
                if (relationship.TableAlias.Equals(relationship.TableName))
                {
                    format = "EXISTS(SELECT 1 FROM {0} WHERE {1}.{2}={3}.{4} AND ";
                }
                else
                {
                    format = "EXISTS(SELECT 1 FROM {0} AS {1} WHERE {1}.{2}={3}.{4} AND ";
                }
                var str = string.Format(format,
                    relationship.TableName, relationship.TableAlias, relationship.ReferenceField,
                    relationship.Parent.TableAlias, relationship.RelatedToField);
                sb.Append(str);

                ParseRelationship(criteria, sb, relationship, propertyQueue);

                sb.Append(")");
            }
        }

        protected virtual string PretreatmentCriteria(Criteria criteria)
        {
            var sb = new StringBuilder("");
            string path = null;
            if (criteria.Factor is Field)
            {
                path = (criteria.Factor as Field).Path;
            }
            else if(criteria.Factor is Formular)
            {
                Formular formular = criteria.Factor as Formular;
                if (formular.HasFieldInside(true))
                    path = formular.AllFields()[0].Path;
            }
            //如果字段路径包含多级(如Supplier.Code)，则生成Exists条件语句
            if (path!=null && path.Contains('.'))
            {
                var config = EntityConfigurationManager.FindEntityConfiguration(this.Query.SourceEntityType.Name);
                var propertyQueue =
                    new Queue<string>(path.Split(new char[] {'.'}, StringSplitOptions.RemoveEmptyEntries));
                ParseRelationship(criteria, sb, config, propertyQueue);
                /*var properties = new List<string>(path.Split(new char[] {'.'}, StringSplitOptions.RemoveEmptyEntries));
                for (int i = properties.Count - 1; i >= 0; i--)
                {
                    var propertyName = properties[i];
                    var relationship = config.Relationships[propertyName];
                    ParseRelationship(sb, relationship);
                }*/
            }
            else
            {
                var s = GetCriteriaPresentation(criteria);
                sb.Append(s);
            }
            return sb.ToString();
        }

        /// <summary>
        /// 获取字段全称
        /// </summary>
        /// <param name="field">字段对象<see cref="Field"/></param>
        /// <returns>全称格式：{表别名}.{字段名}</returns>
        private string GetFieldFullName(Field field)
        {
            if (field == null)
            {
                throw new ArgumentNullException("field");
            }
            var config = field.FindEntityConfiguration(this.Query.SourceEntityType.Name);
            return field.FullName(config);
        }

        /// <summary>
        /// 获取常量<see cref="Constant"/>解析生成的文本
        /// </summary>
        /// <param name="constant"></param>
        /// <returns></returns>
        protected virtual string GetConstantPresentation(Constant constant, Criteria criteria=null)
        {
            return GetValuePresentation(constant.Value, criteria);
        }

        /// <summary>
        /// 获取宏<see cref="Macro"/>解析生成的文本
        /// </summary>
        /// <param name="macro"></param>
        /// <returns></returns>
        protected virtual string GetMacroPresentation(Macro macro, Criteria criteria=null)
        {
            return GetValuePresentation(macro.Execute(), criteria);
        }

        /// <summary>
        /// 获取常量值生成的文本
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        protected virtual string GetValuePresentation(object value, Criteria criteria=null)
        {
            var valueType = value.GetType();
            if (this.UseParameter)
            {
                var parameterName = GenParameterName(criteria==null ? null : criteria.Factor);
                this.parameterValues.Add(parameterName, value);
                return parameterName;
            }
            else
            {
                string text;
                if (valueType == typeof(string))
                {
                    text = string.Format("'{0}'", value);
                }
                else if (valueType == typeof(DateTime))
                {
                    text = string.Format("'{0}'", value);
                }
                else if (valueType == typeof(Boolean))
                {
                    text = Convert.ToBoolean(value) ? "1" : "0";
                }
                else
                {
                    text = value.ToString();
                }
                return text;
            }
        }

        /// <summary>
        /// 获取字段<see cref="Field"/>解析生成的文本
        /// </summary>
        /// <param name="field"></param>
        /// <returns></returns>
        protected virtual string GetFieldPresentation(Field field)
        {
            return GetFieldFullName(field);
        }

        /// <summary>
        /// 获取算式<see cref="Formular"/>解析生成的文本
        /// </summary>
        /// <param name="formular"></param>
        /// <returns></returns>
        protected virtual string GetFormularPresentation(Formular formular)
        {
            var factorStack = new Stack<Factor>();
            var outputStack = new Stack<string>();
            var visitor = new FactorVisitor(formular);
            visitor.Visit(factorStack);
            while (factorStack.Count>0)
            {
                var item = factorStack.Pop();
                if (item is Formular)
                {
                    formular = item as Formular;
                    var left = outputStack.Pop();
                    var right = outputStack.Pop();
                    string format;
                    switch (formular.Operator)
                    {
                        case MathOperator.Plus:
                        case MathOperator.Minus:
                        case MathOperator.Multiply:
                        case MathOperator.Divide:
                            format = "{0}"+formular.Operator.GetOperatorString()+"{1}";
                            break;
                        case MathOperator.Count:
                            format = "COUNT({0})";
                            break;
                        case MathOperator.Sum:
                            format = "SUM({0})";
                            break;
                        case MathOperator.Power:
                            format = "POWER({0},{1})";
                            break;
                        default :
                            throw new NotSupportedException("不支持的运算符："+formular.Operator.ToString());
                    }
                    /*处理是否需要对算式中的子算式加()的逻辑*/
                    if (formular.LeftFactorOperatorIsLower)//如果左因子为算式，则算式运算符优先级较低，则用括号包围
                    {
                        left = "(" + left + ")";
                    }
                    if (formular.RightFactorOperatorIsLower)//如果右因子为算式，则算式运算符优先级较低，则用括号包围
                    {
                        right = "(" + right + ")";
                    }
                    /*if (formular.Unitaried)
                    {
                        format = "(" + format + ")";
                    }*/
                    var str = string.Format(format, left, right);
                    outputStack.Push(str);
                }
                else if (item is Field)
                {
                    var field = item as Field;
                    outputStack.Push(GetFieldFullName(field));
                }
                else if (item is Macro)
                {
                    var macro = item as Macro;
                    outputStack.Push(GetMacroPresentation(macro));
                }
                else if (item is Constant)
                {
                    var constant = item as Constant;
                    outputStack.Push(GetConstantPresentation(constant));
                }
            }
            return outputStack.Pop();
        }

        protected virtual string GetFactorPresentation(Factor factor)
        {
            if (factor is Constant)
            {
                return GetConstantPresentation(factor as Constant);
            }
            else if (factor is Macro)
            {
                return GetMacroPresentation(factor as Macro);
            }
            else if (factor is Field)
            {
                return GetFieldPresentation(factor as Field);
            }
            else if (factor is Formular)
            {
                return GetFormularPresentation(factor as Formular);
            }
            else
            {
                throw new InvalidOperationException();
            }
        }

        protected virtual string GetCriteriaPresentation(Criteria criteria)
        {
            var sb = new StringBuilder("");
            //注意：为了提高SQL脚本可读性，这里先处理右因子，再处理左因子。这样如果左因子中有使用常量的话，最终进行易读性增强处理后生成的参数名序号才不会违反日常编号习惯
            sb.Append(WriteFactor(criteria.Value, criteria));
            sb.Insert(0, WriteOperator(criteria.Operator));
            sb.Insert(0, WriteFactor(criteria.Factor));
            return sb.ToString();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sb"></param>
        /// <param name="factor"></param>
        /// <param name="criteria">因子所属的条件对象，仅在处理右因子时传入，处理左因子应保持此参数值为null</param>
        /// <remarks>传入criteria参数主要是为了处理字符串LIKE运算，以及生成SQL脚本中的参数命名更友好</remarks>
        private string WriteFactor(Factor factor, Criteria criteria = null)
        {
            string text;
            if (factor is Constant) //常量
            {
                text = GetConstantPresentation(factor as Constant, criteria);
            }
            else if (factor is Macro) //宏
            {
                text = GetMacroPresentation(factor as Macro, criteria);
            }
            else if (factor is Field) //字段
            {
                text = GetFieldPresentation(factor as Field);
            }
            else if (factor is Formular) //算式
            {
                text = GetFormularPresentation(factor as Formular);
            }
            else
            {
                throw new Exception("不支持的因子类型："+factor.GetType().FullName);
            }
            if (criteria != null)
            {
                string format;
                bool isStringConst = text.StartsWith("'") && text.EndsWith("'"); //判断因子是否为字符串常量
                if (isStringConst) //如果factor因子表达是字符常量，则先将前后的单引号去除，以便直接添加%号，而不用'%'+'文本'+'%'的形式，使生成的SQL更易读
                {
                    format = "'{0}'";
                    text = text.Substring(1, text.Length - 2);
                }
                else
                {
                    format = "{0}";
                }
                switch (criteria.Operator)
                {
                    case RelationalOperator.Contains:
                        format = isStringConst ? "'{1}{0}{1}'" : "'{1}'+{0}+'{1}'";
                        break;
                    case RelationalOperator.StartsWith:
                        format = isStringConst ? "'{0}{1}'" : "{0}+'{1}'";
                        break;
                    case RelationalOperator.EndsWith:
                        format = isStringConst ? "'{1}{0}'" : "'{1}'+{0}";
                        break;
                }
                text = string.Format(format, text, LikeSymbol);
            }
            return text;
        }


        private string WriteOperator(RelationalOperator operatr)
        {
            string text;
            switch (operatr)
            {
                case RelationalOperator.Contains:
                case RelationalOperator.EndsWith:
                case RelationalOperator.StartsWith:
                    text =" LIKE ";
                    break;
                case RelationalOperator.EqualTo:
                    text =" = ";
                    break;
                case RelationalOperator.GreaterThan:
                    text =" > ";
                    break;
                case RelationalOperator.GreaterThanOrEqualTo:
                    text =" >= ";
                    break;
                case RelationalOperator.LessThan:
                    text =" < ";
                    break;
                case RelationalOperator.LessThanOrEqualTo:
                    text =" <= ";
                    break;
                case RelationalOperator.NotEquals:
                    text =" <> ";
                    break;
                case RelationalOperator.In:
                case RelationalOperator.NotIn:
                    throw new NotImplementedException("尚未实现支持 In 操作符的解析");
                default:
                    throw new Exception(string.Format("{0} 操作符不被识别", operatr));
            }
            return text;
        }

        /*private string GenParameterName(Field field)
        {
            var parameterName = string.Format("{0}{1}", this.ParameterChar, field.Name);
            var copy = parameterName;
            int i = 1;//为了生成SQL语句的易读性，如果参数名已经存在，则第2个参数后缀直接从2开始编，例如两个单据日期参数的名称为：@BillDate, @BillDate2
            while (this.parameterValues.ContainsKey(parameterName))
            {
                i++;
                parameterName = copy + i;
            }
            return parameterName;
        }*/

        private string GenParameterName(Factor factor)
        {
            string incipientName;
            if (factor != null && factor is Field)
            {
                incipientName = string.Format("{0}{1}_", this.ParameterChar, (factor as Field).Name);
            }
            else
            {
                incipientName = string.Format("{0}p_", this.ParameterChar);
            }
            //注意：为了方便之后EnhanceReadability()方法对脚本做增强易读性方面的处理，这里的参数名称在序号前用"_"符号分隔
            string parameterName;
            int iSerial = 2;
            do
            {
                parameterName = incipientName + iSerial;
                iSerial++;
            }
            while (this.parameterValues.ContainsKey(parameterName));
            return parameterName;
        }

        private string EnhanceReadability(string whereClause)
        {
            string lastPrefix = null;
            int iSerial = 0;
            var pvs = parameterValues.OrderByDescending(p => p.Key).ToArray();
            parameterValues.Clear();
            foreach (var kv in pvs)
            {
                string key = kv.Key;
                var i = key.LastIndexOf('_');
                if (i < 0)
                    continue;
                var keyPrefix = key.Remove(i);
                if (!keyPrefix.Equals(lastPrefix))
                {
                    iSerial = 1;
                }
                else
                {
                    iSerial++;
                }
                lastPrefix = keyPrefix;
                var newKey = keyPrefix;
                //为了脚本的易读性，如果第一个字段参数名不添加数字后缀，例如两个单据日期参数的名称为：@BillDate, @BillDate2
                if (iSerial > 1 || newKey == ParameterChar + "p")
                {
                    newKey += iSerial;
                }

                parameterValues.Add(newKey, kv.Value);
                whereClause = whereClause.Replace(key, newKey);
            }
            //for(int i=)
            return whereClause;
        }

        /// <summary>
        /// Performs the compile.
        /// </summary>
        /// <param name="query">查询对象。以参数方式传入，而不是在编译器构造函数时传入主要是基于实体配置信息重用性考虑。
        /// 也可以考虑在GetCriteriaPresentation()方法中增加一个Query类型的参数，取消类的query属性
        /// </param>
        /// <returns></returns>
        protected override string PerformCompile()
        {
            //先清除参数集合
            this.parameterValues.Clear();
            var whereClause = CompileWherePart();
            var orderBy = CompileOrderBy();

            if (!GenSelectPart) //不需要生成SELECT语句，直接返回
            {
                return whereClause + orderBy;
            }
            //如果需要同时生成SELECT语句
            var config = EntityConfigurationManager.FindEntityConfiguration(Query.SourceEntityType.Name);
            var format = config.TableName.Equals(config.TableAlias, StringComparison.InvariantCultureIgnoreCase)
                ? "SELECT * FROM {0}"
                : "SELECT * FROM {0} {1}";//这里不使用TableName AS {alias}的语法，因为Oracle不支持
            var select = string.Format(format, config.TableName, config.TableAlias);
            if (!string.IsNullOrEmpty(whereClause))
            {
                select = select + " WHERE " + whereClause + orderBy;
            }
            return select;
        }

        protected virtual string CompileWherePart()
        {
            if (Query.RootExpression == null)
                return "1=1";

            var objectStack = new Stack<Xpression>();
            var queryStack = new Stack<string>();

            var visitor = new DelegatedQueryVisitor(
                this.Query,
                objectStack.Push,
                objectStack.Push,
                objectStack.Push);
            visitor.Visit();

            while (objectStack.Count > 0)
            {
                var item = objectStack.Pop();
                if (item is Criteria)
                {
                    //queryStack.Push(this.GetCriteriaPresentation(item as Criteria));
                    queryStack.Push(this.PretreatmentCriteria(item as Criteria));
                }
                else if (item is CriteriaNegation)
                {
                    var pair = item as CriteriaNegation;
                    var cachedQuery = queryStack.Pop();
                    queryStack.Push(string.Format(" {0} {1} ", pair.Operator.ToString().ToUpper(), cachedQuery));
                }
                else if (item is CriteriaPair)
                {
                    var pair = item as CriteriaPair;
                    var leftQuery = queryStack.Pop();
                    var rightQuery = queryStack.Pop();
                    /*处理是否需要对算式中的子算式加()的逻辑*/
                    if (pair.LeftExpressionOperatorIsLower)//如果左表达式为条件对，则算式运算符优先级较低，则用括号包围
                    {
                        leftQuery = "(" + leftQuery + ")";
                    }
                    if (pair.RightExpressionOperatorIsLower)//如果右表达式为条件对，则算式运算符优先级较低，则用括号包围
                    {
                        rightQuery = "(" + rightQuery + ")";
                    }
                    var trem = string.Format(
                        "{0} {1} {2}",
                        leftQuery,
                        pair.Operator.ToString().ToUpper(),
                        rightQuery);
                    queryStack.Push(trem);
                }
            }

            string whereClause = queryStack.Pop();
            if (UseParameter)//如果使用参数方式生成脚本，则参数编号是逆序的(因为使用的是栈解析)，调用EnhanceReadability()对脚本中的参数名重新编号，增加脚本可读性
            {
                whereClause = EnhanceReadability(whereClause);
            }
            return whereClause;
        }

        protected virtual string CompileOrderBy()
        {
            if(Query.OrderClause==null || !Query.OrderClause.HasElements)
                return null;
            var sb = new StringBuilder();
            for(int i=0; i<Query.OrderClause.Elements.Count; i++)
            {
                var orderElement = Query.OrderClause.Elements[i];
                if(sb.Length>0)
                    sb.Append(", ");
                sb.Append(WriteFactor(orderElement.Factor));
                if(orderElement.Rule==OrderRule.Descending)
                    sb.Append(" DESC");
            }
            return sb.ToString();
        }

    }
}
